#include "cellNetwork.h"
#include <math.h>

void WriteDotFile(char *fileName, FileOfId route)
{
	FILE *f;
	int limit = 0, distance;

	f = fopen(fileName, "w");

	if (f == NULL)
	{
	    printf("\nCan not create a file with name: %s!\n", fileName);
	    return;
	}

	limit = route.limit;
	distance = 1;
	fprintf(f, "digraph cellGraph{rankdir=LR; size=\"8,5\"node [shape = circle];\n\n");

	for (int i = 0; i < limit - 1; i=i+2)
	{
	   fprintf(f, "%d -> %d [label = \"%d \" color = \"blue\"]\n", route.newFound[i], route.newFound[i+1], distance++);
	}

	fprintf(f, "\n}");
	fclose(f);
}

void InitPacket(PacketType type, Packet *pac, int pac_number, int node_number)
{
	for (int i = 0; i < pac_number; i++)
	{
	    pac[i].destId = rand() % node_number;	    
	    pac[i].srcId = rand() % node_number;
	    while (pac[i].destId == pac[i].srcId)
	    {
		pac[i].srcId = rand() % node_number;
	    }
	    pac[i].packetType = type;
	}
}

void RoutingPacket(Packet pac, TabNeighbours *tabs, canaux *channels, FileOfId &route)
{
	int distance, limit, currentId;
	TabNeighbours tab;
	KnownId knownId;
	FileOfId file, buf;
	
	tab = tabs[pac.srcId];
	limit = tab.limit;

	//Finding the destination. Going back to the root
	distance = -1;
	for (int i = 0; i < limit; i++)
	{
	     knownId = tab.knownTab[i];

	     if (knownId.Id == pac.destId)
	     {
		distance = knownId.Distance;
		break;
	     }
	}

	if (distance == -1)
	{
	    printf("There are not route between %d and %d!\n", pac.srcId, pac.destId);
	    return;
	}

	ResetFile_h(route);
	ResetFile_h(file);
	AddIdInFile_h(pac.destId, file);

	for (int d = distance - 1; d >= 0; d--)
	{
	    ResetFile_h(buf);
	    for (int entry = 0; entry < file.limit; entry++)
	    {
		currentId = file.newFound[entry];
	    	for (int i = tab.limit - 1; i >= 0; i--)
	    	{
		    knownId = tab.knownTab[i];
		   
		    if (knownId.Distance == d)
		    {			
			if (IsLinked_h(knownId.Id, currentId, channels) == true)
			{
			   //printf("%d -> %d : %d\n", knownId.Id, currentId, knownId.Distance);
			   if (FoundIdInFile_h(knownId.Id, buf) == false)
			   {		
		   	  	 AddIdInFile_h(knownId.Id, buf);
			   }
			}
		    }
		    else
		    {
		    	//break;
		    }
	    	}
	    }	    
	    file = buf;
	    //Putting transition rule here to only chose one node of many posible nodes.
	    file.limit = 1;			  
	    AddIdInFile_h(file.newFound[0], route);
	    AddIdInFile_h(currentId, route);

	}
}

/*
void InitConfiguration(int identity, Packet paq)
{
	if (identity == 0)
	{
	   InitPacket(3, 0, SEND, paq);
	}
	else
	{
	   InitPacket(NIL, NIL, NIL, paq);
	}
}
*/

__device__ bool TransitionRule(NodeState cur_state, NodeState next_state)
{
	bool result = false;
	
	result = true;//Defining rule here

	return result;
}

__device__ int FindMaxDistance(TabNeighbours tab)
{
	int max, limit;
	KnownId knownId;
		
	max = 0;
	limit = tab.limit;
	for (int i = 0; i < limit; i++)
	{
	    knownId = tab.knownTab[i];
	    if (knownId.Distance > max)
	    {
		max = knownId.Distance;
	    }
	}
	return max;
}

int FindMaxDistance_h(TabNeighbours tab)
{
	int max, limit;
	KnownId knownId;
		
	max = 0;
	limit = tab.limit;
	for (int i = 0; i < limit; i++)
	{
	    knownId = tab.knownTab[i];
	    if (knownId.Distance > max)
	    {
		max = knownId.Distance;
	    }
	}	
	return max;
}

//The longest routes of a table
void WriteMaxRoutes(char *fileName, TabNeighbours tab, canaux *channels)
{
	int maxDistance, currentId, distance;
	FileOfId file, buf;
	KnownId knownId;
	FILE *f;	

	maxDistance = FindMaxDistance_h(tab);
	ResetFile_h(buf);	

	for (int i = tab.limit - 1; i >= 0; i--)
	{	   
	    knownId = tab.knownTab[i];
	   
	    if (knownId.Distance == maxDistance)
	    {
		AddIdInFile_h(knownId.Id, file);
	    }
	    else
	    {
		break;
	    }
	}
	
	f = fopen(fileName, "w");
	if (f == NULL)
	{
	    return;
	}

	fprintf(f, "digraph cellGraph{rankdir=LR; size=\"8,5\"node [shape = circle];\n\n");
	//Looping each distance
	distance = maxDistance - 1;
	for (int d = distance; d >= 0; d--)
	{
	    ResetFile_h(buf);	  
	    for (int entry = 0; entry < file.limit; entry++)
	    {
		currentId = file.newFound[entry];
	    	for (int i = tab.limit - 1; i >= 0; i--)
	    	{
		    knownId = tab.knownTab[i];
		   
		    if (knownId.Distance == d)
		    {			
			if (IsLinked_h(knownId.Id, currentId, channels) == true)
			{		   
			   fprintf(f, "%d -> %d [label = \"%d \" color = \"blue\"]\n", knownId.Id, currentId, knownId.Distance + 1);
			   if (FoundIdInFile_h(knownId.Id, buf) == false)
			   {		
		   	  	 AddIdInFile_h(knownId.Id, buf);
			   }
			}
		    }
		    else
		    {
		    	//break;
		    }
	    	}
	    }
	    file = buf;
	}
	fprintf(f, "\n}");
	fclose(f);
}

void WriteRouteTable(char *fileName, TabNeighbours tab, canaux *channels)
{	
	int limit = 0;
	FILE *f;
	KnownId knownId;
	limit = tab.limit;
	FileOfId file, buf;
	int distance = 0;

	f = fopen(fileName, "w");
	if (f == NULL)
	{
	    return;
	}
	
	fprintf(f, "digraph cellGraph{rankdir=LR; size=\"8,5\"node [shape = circle];\n\n");

	ResetFile_h(file);
	AddIdInFile_h(tab.knownTab[0].Id, file);
	distance = FindMaxDistance_h(tab);

	for (int i = 1; i <= distance; i++)
	{	
 	    ResetFile_h(buf);
	    for (int entry = 0; entry < file.limit; entry++)
	    {
		for (int j = 0; j < limit; j++)
		{
		     knownId = tab.knownTab[j];
	              if (knownId.Distance == i)//The same Distance
	              {
			if (IsLinked_h(file.newFound[entry], knownId.Id, channels) == true)
			{
			    fprintf(f, "%d -> %d [label = \"%d \" color = \"blue\"]\n", file.newFound[entry], knownId.Id, knownId.Distance);
			    if (FoundIdInFile_h(knownId.Id, buf) == false)
			    {
			    	AddIdInFile_h(knownId.Id, buf);
			    }
			}
		      }
		}
	     }    

	     file = buf;	
		
	  }

	  fprintf(f, "\n}");
	  fclose(f);	
}

void WriteNetwork(char *fileName, canaux *channels, int node_number)
{
	FILE *f;
	int nbOut, node, indexLink;
	Direction direction;
	f = fopen(fileName,"w");

	if (f == NULL)
	{
	   return;
	}
	fprintf(f, "digraph cellGraph{rankdir=LR;size=\"8,5\"node [shape = circle];\n\n");
	for (int i = 0; i < node_number; i++)
	{
	    nbOut = channels[i].nbOut;	
	    for (int j = 0; j < nbOut; j++)
	    {
	        node = channels[i].read[j].node;
		direction = channels[i].read[j].direction;
	        indexLink = j;
	        fprintf(f, "%d -> %d [label = \"%d (%d,%d) \" color = \"blue\"]; \n", 
			i, node, indexLink, direction.x, direction.y);
	    }		
	}
	fprintf(f, "\n}");
	fclose(f);	
}

__device__ bool FoundIdInFile(int id, FileOfId file)
{
	bool found = false;
	int limit = file.limit;
	
	for (int i = 0; i < limit; i++)
	{
	    if (id == file.newFound[i])
	    {
		found = true;
		break;
	    }
	}
	return found;
}

bool FoundIdInFile_h(int id, FileOfId file)
{
	bool found = false;
	int limit = file.limit;
	
	for (int i = 0; i < limit; i++)
	{
	    if (id == file.newFound[i])
	    {
		found = true;
		break;
	    }
	}
	return found;
}

__device__ bool IsLinked(int src, int dest, canaux *channels)
{
     	bool linked = false;
     	int nbOut;
	
	nbOut = channels[src].nbOut;
	for (int i = 0; i < nbOut; i++)
	{
	    if (channels[src].read[i].node == dest)	
	    {
		linked = true;
		break;
	    }
	}
	return linked;
}

bool IsLinked_h(int src, int dest, canaux *channels)
{
     	bool linked = false;
     	int nbOut;
	
	nbOut = channels[src].nbOut;
	for (int i = 0; i < nbOut; i++)
	{
	    if (channels[src].read[i].node == dest)	
	    {
		linked = true;
		break;
	    }
	}
	return linked;
}

__device__ void ResetFile(FileOfId &file)
{
	file.limit = 0;
}

void ResetFile_h(FileOfId &file)
{
	file.limit = 0;
}

__device__ void ResetTab(TabNeighbours &tab)
{
	tab.limit = 0;
}

__device__ void AddIdInFile(int id, FileOfId &file)
{
	int limit;
	limit = file.limit;
	file.newFound[limit] = id;
	file.limit = limit + 1;
}

void AddIdInFile_h(int id, FileOfId &file)
{
	int limit;
	limit = file.limit;
	file.newFound[limit] = id;
	file.limit = limit + 1;
}

void DumpFile(FileOfId file)
{
	int limit = file.limit;

	for (int i = 0; i < limit; i++)
	{
	    printf("%d ", file.newFound[i]);
	}
}

void DumpTab(TabNeighbours *tabs, int node_number)
{
	int limit;
	char fileName[] = "RoutingTable.txt";
	KnownId knownId;
	int identity;
	FILE *f;
	f = fopen(fileName, "w");
	if (f != NULL)
	{
	   printf("\nWriting content into %s file ...\n", fileName);
    	   for (int i = 0; i < node_number; i++)
	   {	     
	       identity = tabs[i].knownTab[0].Id;
	       fprintf(f, "P%-5d: ", identity);
	       limit = tabs[i].limit;
	       for (int j = 0; j < limit; j++)
	       {
	     	   knownId = tabs[i].knownTab[j];
		   fprintf(f, "(%d - %d - %d)", knownId.Id, knownId.Distance, knownId.IndexLink);
	       }
	       fprintf(f, "\n");	     
	   }
	   printf("File is written!\n");
	}
	fclose(f);
}

__device__ void AddIdInTab(int id, int distance, int indexLink, TabNeighbours &tab)
{
	
	int limit;
	KnownId anId;
	anId.Id = id;
	anId.Distance = distance;
	anId.IndexLink = indexLink;
	limit = tab.limit;
	tab.knownTab[limit] = anId;
	tab.limit = limit + 1;
}

__device__ bool FoundIdInTab(int id, TabNeighbours tab)
{
	int limit;
	KnownId anId;
	bool found = false;

	found = false;
	limit = tab.limit;

	for (int i = 0; i < limit; i++)
	{
	    anId = tab.knownTab[i];
	    if (anId.Id == id)
	    {
		found = true;
		break;
	    }
	}
	return found;
}

__global__ void Init(TabNeighbours *tabs, Buffer *buffer, canaux *channels_d, int node_number)
{
	FileOfId ourFile;
	int ourId;
	int idx;

	idx = blockIdx.x * blockDim.x + threadIdx.x;

	if (idx < node_number)
	{
	   ourId = idx;
	   ResetFile(ourFile);
	   AddIdInFile(ourId, ourFile);
	   ResetTab(tabs[idx]);
	   AddIdInTab(ourId, 0, -1, tabs[idx]);
	   //Sending	  
	   buffer[idx].currentFiles[1] = ourFile;
	}
}

__global__ void Distances(TabNeighbours *tabs, Buffer *buffer, canaux *channels_d, int node_number, int turn)
{	
	FileOfId ourFile, thisFile;
	int thisLimit;
	int anInputId;
	int idx, nbIn, nodeIn;
	bool found = false;

	idx = blockIdx.x * blockDim.x + threadIdx.x;

	if (idx < node_number)
	{
	   nbIn = channels_d[idx].nbIn;
	   //Using data from last step
	   buffer[idx].currentFiles[0] = buffer[idx].currentFiles[1];

	   /*******************Communicating************************/
	   //Receiving
	   ResetFile(ourFile);
	   int indexLink;
	   for (indexLink = 0; indexLink < nbIn; indexLink++)
	   {
		nodeIn = channels_d[idx].read[indexLink].node;
		thisFile = buffer[nodeIn].currentFiles[0];
		thisLimit = thisFile.limit;

		for (int entry = 0; entry < thisLimit; entry++)
		{
		    anInputId = thisFile.newFound[entry];
		    found = FoundIdInTab(anInputId, tabs[idx]);
		    if (found == false)
		    {
			AddIdInFile(anInputId, ourFile);
			AddIdInTab(anInputId, turn + 1, indexLink, tabs[idx]);
		    }
		}
	   }
	   //Sending
	   buffer[idx].currentFiles[1] = ourFile;
	}	
}

__global__ void GettingMaxLoc(TabNeighbours *tabs, Buffer *buffer, int node_number)
{
	int idx, max, limit;

	idx = blockIdx.x * blockDim.x + threadIdx.x;

	if (idx < node_number)
	{
	   max = 0;
	   limit = tabs[idx].limit;
	   for (int i = 0; i < limit; i++)
	   {
	    	if (max < tabs[idx].knownTab[i].Distance)
		{
		   max = tabs[idx].knownTab[i].Distance;
		}
	   }
	   buffer[idx].maxLoc = max;
	   buffer[idx].maxGlobal = max;
	}
}

__global__ void ComputeMaxGlobal(Buffer *buffer, canaux *channels_d, int node_number)
{
	int idx, nbIn, nodeIn, maxIn, max;

	idx = blockIdx.x * blockDim.x + threadIdx.x;

	if (idx < node_number)
	{
	   max = buffer[idx].maxGlobal;
	   nbIn = channels_d[idx].nbIn;
	   //Receiving
	   for (int i = 0; i < nbIn; i++)
	   {
		nodeIn = channels_d[idx].read[i].node;
		maxIn = buffer[nodeIn].maxGlobal;
		if (maxIn > max)
		{
		   max = maxIn;
		}
	   }
	   //Updating maxGlobal
	   buffer[idx].maxGlobal = max;
	   buffer[idx].maxGlobal = max;	   
	}	
}

void DumpMaxLoc(Buffer *buffer, int node_number)
{
	printf("Node - MaxLoc - MaxGlobal\n");
	for (int i = 0; i < node_number; i++)
	{
	    printf("%-5d - %-6d -  %d\n", i, buffer[i].maxLoc, buffer[i].maxGlobal);
	}
}
